#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "../inc/threadpool.h"


int threadpool_free(threadpool_t *pool);
void *threadpool_thread(void *threadpool);

threadpool_t *threadpool_create(int thread_count, int queue_size) {
    threadpool_t *pool;
    int i;

    if(thread_count <= 0 || thread_count > 64 || queue_size <= 0 || queue_size > 1024) {
        return NULL;
    }

    pool = (threadpool_t *)malloc(sizeof(threadpool_t));
    if (pool == NULL) {
        return NULL;
    }

    pool->thread_count = thread_count;
    pool->queue_size = queue_size;
    pool->shutdown = pool->started = 0;

    pool->threads = (pthread_t *)malloc(sizeof(pthread_t) * thread_count);
    pool->queue = NULL;

    if((pthread_mutex_init(&(pool->lock), NULL) != 0) ||
       (pthread_cond_init(&(pool->notify), NULL) != 0) ||
       (pool->threads == NULL)) {
        return NULL;
    }

    for(i = 0; i < thread_count; i++) {
        if(pthread_create(&(pool->threads[i]), NULL,
                          threadpool_thread, (void*)pool) != 0) {
            threadpool_destroy(pool, 0);
            return NULL;
        }
        pool->started++;
    }

    return pool;
}

int threadpool_add(threadpool_t *pool, void (*function)(void *), void *arg) {
    task_t *newTask;

    if(pool == NULL || function == NULL) {
        return -1;
    }

    newTask = (task_t *)malloc(sizeof(task_t));
    if (newTask == NULL) {
        return -1;
    }
    newTask->function = function;
    newTask->arg = arg;
    newTask->next = NULL;

    pthread_mutex_lock(&(pool->lock));

    if(pool->queue == NULL) {
        pool->queue = newTask;
    } else {
        task_t *old = pool->queue;
        while(old->next) {
            old = old->next;
        }
        old->next = newTask;
    }

    pthread_cond_signal(&(pool->notify));
    pthread_mutex_unlock(&(pool->lock));

    return 0;
}

void *threadpool_thread(void *threadpool) {
    threadpool_t *pool = (threadpool_t *)threadpool;
    task_t *task;

    while(1) {
        pthread_mutex_lock(&(pool->lock));

        while((pool->queue == NULL) && (!pool->shutdown)) {
            pthread_cond_wait(&(pool->notify), &(pool->lock));
        }

        if(pool->shutdown) {
            break;
        }

        task = pool->queue;
        if (task != NULL) {
            pool->queue = task->next;
        }

        pthread_mutex_unlock(&(pool->lock));

        (*(task->function))(task->arg);
        free(task);
    }

    pool->started--;
    pthread_mutex_unlock(&(pool->lock));
    pthread_exit(NULL);
    return(NULL);
}

int threadpool_destroy(threadpool_t *pool, int flags) {
    if(pool == NULL || pool->started == 0) {
        return -1;
    }

    pthread_mutex_lock(&(pool->lock));
    pool->shutdown = 1;
    pthread_cond_broadcast(&(pool->notify));
    pthread_mutex_unlock(&(pool->lock));

    for(int i = 0; i < pool->thread_count; i++) {
        pthread_join(pool->threads[i], NULL);
    }

    threadpool_free(pool);

    return 0;
}

int threadpool_free(threadpool_t *pool) {
    if(pool == NULL || pool->started > 0) {
        return -1;
    }

    if(pool->threads) {
        free(pool->threads);
        pthread_mutex_lock(&(pool->lock));
        pthread_mutex_destroy(&(pool->lock));
        pthread_cond_destroy(&(pool->notify));
        pthread_mutex_unlock(&(pool->lock));
    }
    free(pool);
    return 0;
}
